package xlog

import (
	"encoding/json"
	"fmt"
	"os"
	"runtime"
	"strings"

	"github.com/sirupsen/logrus"
)

var (
	// the local logger
	logger *logrus.Logger = logrus.New()

	// default log level is info
	level = logrus.TraceLevel

	// add service name for all messages
	service string
)

const (
	// PanicLevel level, highest level of severity. Logs and then calls panic with the
	// message passed to Debug, Info, ...
	PanicLevel logrus.Level = iota
	// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
	// logging level is set to Panic.
	FatalLevel
	// ErrorLevel level. Logs. Used for errors that should definitely be noted.
	// Commonly used for hooks to send errors to an error tracking service.
	ErrorLevel
	// WarnLevel level. Non-critical entries that deserve eyes.
	WarnLevel
	// InfoLevel level. General operational entries about what's going on inside the
	// application.
	InfoLevel
	// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
	DebugLevel
	// TraceLevel level. Designates finer-grained informational events than the Debug.
	TraceLevel
)

// logCallerfilePath returns a package/file:line description of the caller,
// preserving only the leaf directory name and file name.
func logCallerfilePath(loggingFilePath string) string {
	// To make sure we trim the path correctly on Windows too, we
	// counter-intuitively need to use '/' and *not* os.PathSeparator here,
	// because the path given originates from Go stdlib, specifically
	// runtime.Caller() which (as of Mar/17) returns forward slashes even on
	// Windows.
	//
	// See https://github.com/golang/go/issues/3335
	// and https://github.com/golang/go/issues/18151
	//
	// for discussion on the issue on Go side.
	idx := strings.LastIndexByte(loggingFilePath, '/')
	if idx == -1 {
		return loggingFilePath
	}
	idx = strings.LastIndexByte(loggingFilePath[:idx], '/')
	if idx == -1 {
		return loggingFilePath
	}
	return loggingFilePath[idx+1:]
}

func callerPrettyfier(f *runtime.Frame) (string, string) {
	filename := logCallerfilePath(f.File)
	return "", fmt.Sprintf("%s:%d", filename, f.Line)
}

func init() {
	// JSON
	// logger.SetReportCaller(false)
	// logger.SetFormatter(&JSONFormatter{
	// 	TimestampFormat: "2006-01-02 15:04:05.000",
	// 	PrettyPrint:     false,
	// 	CallerPrettyfier: func(f *runtime.Frame) (string, string) {
	// 		s := strings.Split(f.Function, ".")
	// 		funcname := s[len(s)-1]
	// 		_, filename := path.Split(f.File)
	// 		return funcname, filename
	// 	},
	// })

	// MICRO
	// logger.SetFormatter(&TextFormatter{
	// 	TimestampFormat: "2006-01-02 15:04:05.000",
	// 	FullTimestamp:   true,
	// 	PadLevelText:    false,
	// 	DisableColors:   true,
	// 	DisableQuote:    true,
	// 	SortingFunc: func(keys []string) []string {
	// 		if len(service) > 0 {
	// 			return []string{
	// 				"time", "level", "file", "service", "msg",
	// 			}
	// 		}
	// 		return []string{
	// 			"time", "level", "file", "msg",
	// 		}
	// 	},
	// 	CallerPrettyfier: func(f *runtime.Frame) (string, string) {
	// 		filename := logCallerfilePath(f.File)
	// 		return "", fmt.Sprintf("%s:%d", filename, f.Line)
	// 	},
	// })

	logger.SetFormatter(&TextFormatter{
		TimestampFormat:  "2006-01-02 15:04:05.000",
		FullTimestamp:    true,
		PadLevelText:     false,
		DisableColors:    false,
		DisableQuote:     true,
		CallerPrettyfier: callerPrettyfier,
	})
	logger.SetReportCaller(true)
	NewCallerHook(logger)
}

// SetCaller 是否显示调用位置
func SetCaller(isReport bool) {
	logger.SetReportCaller(isReport)
}

// SetLoki
func SetLoki(uri string) error {
	loki, err := NewLoki(uri, service, 102400, 1)
	if err != nil {
		return err
	}
	logger.AddHook(loki)
	return nil
}

// Log makes use of github.com/go-log/log.Log
func Log(v ...interface{}) {
	if len(service) > 0 {
		logger.WithFields(logrus.Fields{"service": service}).Log(logrus.InfoLevel, v...)
	} else {
		logger.Log(logrus.InfoLevel, v...)
	}
}

// Logf makes use of github.com/go-log/log.Logf
func Logf(format string, v ...interface{}) {
	if len(service) > 0 {
		logger.WithFields(logrus.Fields{"service": service}).Logf(logrus.InfoLevel, format, v...)
	} else {
		logger.Logf(logrus.InfoLevel, format, v...)
	}
}

// WithLevel logs with the level specified
func WithLevel(l logrus.Level, v ...interface{}) {
	if l > level {
		return
	}

	if len(service) > 0 {
		logger.WithFields(logrus.Fields{"service": service}).Log(l, v...)
	} else {
		logger.Log(l, v...)
	}

}

// WithLevelf logs with the level specified
func WithLevelf(l logrus.Level, format string, v ...interface{}) {
	if l > level {
		return
	}

	if len(service) > 0 {
		logger.WithFields(logrus.Fields{"service": service}).Logf(l, format, v...)
	} else {
		logger.Logf(l, format, v...)

	}
}

// Trace provides trace level logging
func Trace(v ...interface{}) {
	WithLevel(logrus.TraceLevel, v...)
}

// Tracef provides trace level logging
func Tracef(format string, v ...interface{}) {
	WithLevelf(logrus.TraceLevel, format, v...)
}

// Debug provides debug level logging
func Debug(v ...interface{}) {
	WithLevel(logrus.DebugLevel, v...)
}

// Debugf provides debug level logging
func Debugf(format string, v ...interface{}) {
	WithLevelf(logrus.DebugLevel, format, v...)
}

// Warn provides warn level logging
func Warn(v ...interface{}) {
	WithLevel(logrus.WarnLevel, v...)
}

// Warnf provides warn level logging
func Warnf(format string, v ...interface{}) {
	WithLevelf(logrus.WarnLevel, format, v...)
}

// Info provides info level logging
func Info(v ...interface{}) {
	WithLevel(logrus.InfoLevel, v...)
}

// Infof provides info level logging
func Infof(format string, v ...interface{}) {
	WithLevelf(logrus.InfoLevel, format, v...)
}

// Error provides warn level logging
func Error(v ...interface{}) {
	WithLevel(logrus.ErrorLevel, v...)
}

// Errorf provides warn level logging
func Errorf(format string, v ...interface{}) {
	WithLevelf(logrus.ErrorLevel, format, v...)
}

// Fatal logs with Log and then exits with os.Exit(1)
func Fatal(v ...interface{}) {
	WithLevel(logrus.FatalLevel, v...)
	os.Exit(1)
}

// Fatalf logs with Logf and then exits with os.Exit(1)
func Fatalf(format string, v ...interface{}) {
	WithLevelf(logrus.FatalLevel, format, v...)
	os.Exit(1)
}

// Pretty provides pretty trace level logging
func Pretty(v ...interface{}) {
	b, _ := json.MarshalIndent(v, "", "  ")
	WithLevel(logrus.TraceLevel, "\n", string(b))
}

// Prettyf provides pretty trace level logging
func Prettyf(format string, v ...interface{}) {
	b, _ := json.MarshalIndent(v, "", "  ")
	WithLevelf(logrus.TraceLevel, format, string(b))
}

// PrettyLog provides pretty logging with level specified
func PrettyLog(l logrus.Level, v ...interface{}) {
	b, _ := json.MarshalIndent(v, "", "  ")
	WithLevel(l, "\n", string(b))
}

// PrettyLogf provides pretty logging with level specified
func PrettyLogf(l logrus.Level, format string, v ...interface{}) {
	b, _ := json.MarshalIndent(v, "", "  ")
	WithLevelf(l, format, string(b))
}

// GetLogger returns the local logger
func GetLogger() *logrus.Logger {
	return logger
}

// SetLevel sets the log level
func SetLevel(l string) {
	switch l {
	case "trace":
		level = logrus.TraceLevel
	case "debug":
		level = logrus.DebugLevel
	case "warn":
		level = logrus.WarnLevel
	case "info":
		level = logrus.InfoLevel
	case "error":
		level = logrus.ErrorLevel
	case "fatal":
		level = logrus.FatalLevel
	default:
		level = logrus.TraceLevel
	}
	logger.SetLevel(level)
}

// GetLevel returns the current level
func GetLevel() logrus.Level {
	return level
}

// Name Set service name
func Name(name string) {
	service = fmt.Sprintf("%s", name)
}

// RpcTrace rpc logger
func RpcTrace(method string, request interface{}, response interface{}) {
	req, _ := json.MarshalIndent(request, "", "  ")
	rsp, _ := json.MarshalIndent(response, "", "  ")
	WithLevel(logrus.TraceLevel, fmt.Sprintf("%s\nrequest:\n%s\nresponse:\n%s", method, string(req), string(rsp)))
}
